package au.com.handsunited.motherofalldaos.dao.hibernate;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import au.com.handsunited.motherofalldaos.dao.BaseDao;
import au.com.handsunited.paginationutils.HibernatePage;
import au.com.handsunited.paginationutils.Order;
import au.com.handsunited.paginationutils.Page;
import au.com.handsunited.paginationutils.PageImpl;

/**
 * @author Arun Chandrasekhar
 */
public abstract class AbstractBaseHibernateDao<T> extends HibernateDaoSupport
		implements BaseDao<T> {

	private final Class<T> type;

	private static Logger logger = LoggerFactory
			.getLogger(AbstractBaseHibernateDao.class);

	/**
	 *
	 */
	@SuppressWarnings("unchecked")
	public AbstractBaseHibernateDao() {

		type = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * @param type
	 */
	public AbstractBaseHibernateDao(Class<T> type) {
		super();
		this.type = type;
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#create(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T create(T newInstance) {

		return (T) getSession().merge(newInstance);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#read(java.lang.Long)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T read(Long id) {

		return (T) getSession().get(type, id);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#update(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T update(T transientObject) {

		return (T) getSession().merge(transientObject);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#delete(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities)
	 */
	@Override
	public void delete(T persistentObject) {

		getSession().delete(persistentObject);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll()
	 */
	@Override
	public Page<T> findAll() {

		return findByCriteria();
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll(int, int)
	 */
	@Override
	public Page<T> findAll(int pageNumber, int pageSize) {

		return findByCriteria(pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll(org.hibernate.criterion.Order)
	 */
	@Override
	public Page<T> findAll(Order orderBy) {

		List<Order> orderByList = new ArrayList<Order>();
		orderByList.add(orderBy);
		return findAll(orderByList);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll(Order, int,
	 *      int)
	 */
	@Override
	public Page<T> findAll(Order order, int pageNumber, int pageSize) {

		Criteria crit = getSession().createCriteria(getPersistentClass());

		if (order != null) {
			crit.addOrder(order.toHibernateOrder());
		}

		return new HibernatePage<T>(crit, pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll(java.util.List)
	 */
	@Override
	public Page<T> findAll(List<Order> orderByClause) {

		return findByCriteria(orderByClause);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findAll(java.util.List,
	 *      int, int)
	 */
	public Page<T> findAll(List<Order> orderByClause, int pageNumber,
			int pageSize) {

		return findByCriteria(orderByClause, pageNumber, pageSize);
	}

	/**
	 * @param orderByClause
	 * @return
	 */
	private PageImpl<T> findByCriteria(List<Order> orderByClause) {

		Criteria crit = getSession().createCriteria(getPersistentClass());

		for (Order order : orderByClause) {
			crit = crit.addOrder(order.toHibernateOrder());
		}

		return new HibernatePage<T>(crit);
	}

	/**
	 * @param orderByClause
	 * @param page
	 * @param pageSize
	 * @return
	 */
	private Page<T> findByCriteria(List<Order> orderByClause, int pageNumber,
			int pageSize) {

		Criteria crit = getSession().createCriteria(getPersistentClass());

		for (Order order : orderByClause) {
			crit = crit.addOrder(order.toHibernateOrder());
		}

		return new HibernatePage<T>(crit, pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities)
	 */
	@Override
	public Page<T> findByExample(T example) {

		return findByExample(example, null);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      int, int)
	 */
	@Override
	public Page<T> findByExample(T example, int pageNumber, int pageSize) {

		return findByExample(example, Order.asc("name"), pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      au.com.handsunited.bhajanswarupa.utils.Order, int, int)
	 */
	@Override
	public Page<T> findByExample(T example, Order order, int pageNumber,
			int pageSize) {

		List<Order> orderList = new ArrayList<Order>();
		orderList.add(order);

		return findByExample(example, null, orderList, pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      java.lang.String[])
	 */
	@Override
	public Page<T> findByExample(T exampleInstance, String[] excludeProperty) {

		return findByExample(exampleInstance, excludeProperty,
				new ArrayList<Order>());
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      java.lang.String[], int, int)
	 */
	@Override
	public Page<T> findByExample(T exampleInstance, String[] excludeProperty,
			int pageNumber, int pageSize) {

		return findByExample(exampleInstance, excludeProperty,
				new ArrayList<Order>(), pageNumber, pageSize);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      java.lang.String[], java.util.List)
	 */
	@Override
	public Page<T> findByExample(T exampleInstance, String[] excludeProperty,
			List<Order> orderByClause) {

		Criteria crit = getSession().createCriteria(getPersistentClass());

		for (Order order : orderByClause) {
			crit = crit.addOrder(order.toHibernateOrder());
		}

		Example example = Example.create(exampleInstance).enableLike(
				MatchMode.ANYWHERE).ignoreCase();

		if (excludeProperty != null) {
			for (String exclude : excludeProperty) {
				example.excludeProperty(exclude);
			}
		}

		example.excludeProperty("createdOn");
		example.excludeProperty("active");

		crit.add(example);

		return new HibernatePage<T>(crit);
	}

	/**
	 * @see au.com.handsunited.bhajanswarupa.dao.BaseDao#findByExample(au.com.handsunited.bhajanswarupa.model.MotherOfAllEntities,
	 *      java.lang.String[], java.util.List, int, int)
	 */
	@Override
	public Page<T> findByExample(T exampleInstance, String[] excludeProperty,
			List<Order> orderByClause, int pageNumber, int pageSize) {

		Criteria crit = getSession().createCriteria(getPersistentClass());

		for (Order order : orderByClause) {
			crit = crit.addOrder(order.toHibernateOrder());
		}

		Example example = Example.create(exampleInstance).enableLike(
				MatchMode.ANYWHERE).ignoreCase();

		if (excludeProperty != null) {
			for (String exclude : excludeProperty) {
				example.excludeProperty(exclude);
			}
		}

		example.excludeProperty("createdOn");
		example.excludeProperty("active");

		crit.add(example);

		return new HibernatePage<T>(crit, pageNumber, pageSize);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	protected Page<T> findByCriteria(Criterion... criterion) {

		Criteria crit = getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterion) {
			crit.add(c);
		}

		return new HibernatePage<T>(crit);
	}

	/**
	 * @param pageNumber
	 * @param pageSize
	 * @param criterions
	 * @return
	 */
	protected Page<T> findByCriteria(int pageNumber, int pageSize,
			Criterion... criterions) {

		Criteria crit = getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterions) {
			crit.add(c);
		}

		return new HibernatePage<T>(crit, pageNumber, pageSize);
	}

	/**
	 * @return
	 */
	public Class<T> getPersistentClass() {

		return type;
	}

}
